/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.rop.code;

import java.util.HashMap;

import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstString;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeBearer;
import com.android.dx.util.ToHuman;

/**
 * Combination of a register number and a type, used as the sources and
 * destinations of register-based operations.
 */
public final class RegisterSpec implements TypeBearer, ToHuman,
		Comparable<RegisterSpec> {

	/** {@code non-null;} string to prefix register numbers with */
	public static final String PREFIX = "v";

	/** {@code non-null;} intern table for instances */
	private static final HashMap<Object, RegisterSpec> theInterns = new HashMap<Object, RegisterSpec>(
			1000);

	/** {@code non-null;} common comparison instance used while interning */
	private static final ForComparison theInterningItem = new ForComparison();

	/** {@code >= 0;} register number */
	private final int reg;

	/** {@code non-null;} type loaded or stored */
	private final TypeBearer type;

	/**
	 * {@code null-ok;} local variable info associated with this register, if
	 * any
	 */
	private final LocalItem local;

	/**
	 * Intern the given triple as an instance of this class.
	 * 
	 * @param reg
	 *            {@code >= 0;} the register number
	 * @param type
	 *            {@code non-null;} the type (or possibly actual value) which is
	 *            loaded from or stored to the indicated register
	 * @param local
	 *            {@code null-ok;} the associated local variable, if any
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	private static RegisterSpec intern(int reg, TypeBearer type, LocalItem local) {
		synchronized (theInterns) {
			theInterningItem.set(reg, type, local);
			RegisterSpec found = theInterns.get(theInterningItem);

			if (found != null) {
				return found;
			}

			found = theInterningItem.toRegisterSpec();
			theInterns.put(found, found);
			return found;
		}
	}

	/**
	 * Returns an instance for the given register number and type, with no
	 * variable info. This method is allowed to return shared instances (but
	 * doesn't necessarily do so).
	 * 
	 * @param reg
	 *            {@code >= 0;} the register number
	 * @param type
	 *            {@code non-null;} the type (or possibly actual value) which is
	 *            loaded from or stored to the indicated register
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpec make(int reg, TypeBearer type) {
		return intern(reg, type, null);
	}

	/**
	 * Returns an instance for the given register number, type, and variable
	 * info. This method is allowed to return shared instances (but doesn't
	 * necessarily do so).
	 * 
	 * @param reg
	 *            {@code >= 0;} the register number
	 * @param type
	 *            {@code non-null;} the type (or possibly actual value) which is
	 *            loaded from or stored to the indicated register
	 * @param local
	 *            {@code non-null;} the associated local variable
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpec make(int reg, TypeBearer type, LocalItem local) {
		if (local == null) {
			throw new NullPointerException("local  == null");
		}

		return intern(reg, type, local);
	}

	/**
	 * Returns an instance for the given register number, type, and variable
	 * info. This method is allowed to return shared instances (but doesn't
	 * necessarily do so).
	 * 
	 * @param reg
	 *            {@code >= 0;} the register number
	 * @param type
	 *            {@code non-null;} the type (or possibly actual value) which is
	 *            loaded from or stored to the indicated register
	 * @param local
	 *            {@code null-ok;} the associated variable info or null for none
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static RegisterSpec makeLocalOptional(int reg, TypeBearer type,
			LocalItem local) {

		return intern(reg, type, local);
	}

	/**
	 * Gets the string form for the given register number.
	 * 
	 * @param reg
	 *            {@code >= 0;} the register number
	 * @return {@code non-null;} the string form
	 */
	public static String regString(int reg) {
		return PREFIX + reg;
	}

	/**
	 * Constructs an instance. This constructor is private. Use {@link #make}.
	 * 
	 * @param reg
	 *            {@code >= 0;} the register number
	 * @param type
	 *            {@code non-null;} the type (or possibly actual value) which is
	 *            loaded from or stored to the indicated register
	 * @param local
	 *            {@code null-ok;} the associated local variable, if any
	 */
	private RegisterSpec(int reg, TypeBearer type, LocalItem local) {
		if (reg < 0) {
			throw new IllegalArgumentException("reg < 0");
		}

		if (type == null) {
			throw new NullPointerException("type == null");
		}

		this.reg = reg;
		this.type = type;
		this.local = local;
	}

	/** {@inheritDoc} */
	@Override
	public boolean equals(Object other) {
		if (!(other instanceof RegisterSpec)) {
			if (other instanceof ForComparison) {
				ForComparison fc = (ForComparison) other;
				return equals(fc.reg, fc.type, fc.local);
			}
			return false;
		}

		RegisterSpec spec = (RegisterSpec) other;
		return equals(spec.reg, spec.type, spec.local);
	}

	/**
	 * Like {@code equals}, but only consider the simple types of the registers.
	 * That is, this compares {@code getType()} on the types to ignore whatever
	 * arbitrary extra stuff might be carried around by an outer
	 * {@link TypeBearer}.
	 * 
	 * @param other
	 *            {@code null-ok;} spec to compare to
	 * @return {@code true} iff {@code this} and {@code other} are equal in the
	 *         stated way
	 */
	public boolean equalsUsingSimpleType(RegisterSpec other) {
		if (!matchesVariable(other)) {
			return false;
		}

		return (reg == other.reg);
	}

	/**
	 * Like {@link #equalsUsingSimpleType} but ignoring the register number.
	 * This is useful to determine if two instances refer to the "same" local
	 * variable.
	 * 
	 * @param other
	 *            {@code null-ok;} spec to compare to
	 * @return {@code true} iff {@code this} and {@code other} are equal in the
	 *         stated way
	 */
	public boolean matchesVariable(RegisterSpec other) {
		if (other == null) {
			return false;
		}

		return type.getType().equals(other.type.getType())
				&& ((local == other.local) || ((local != null) && local
						.equals(other.local)));
	}

	/**
	 * Helper for {@link #equals} and {@link #ForComparison.equals}, which actually
	 * does the test.
	 * 
	 * @param reg
	 *            value of the instance variable, for another instance
	 * @param type
	 *            value of the instance variable, for another instance
	 * @param local
	 *            value of the instance variable, for another instance
	 * @return whether this instance is equal to one with the given values
	 */
	private boolean equals(int reg, TypeBearer type, LocalItem local) {
		return (this.reg == reg)
				&& this.type.equals(type)
				&& ((this.local == local) || ((this.local != null) && this.local
						.equals(local)));
	}

	/**
	 * Compares by (in priority order) register number, unwrapped type (that is
	 * types not {@link TypeBearer}s, and local info.
	 * 
	 * @param other
	 *            {@code non-null;} spec to compare to
	 * @return {@code -1..1;} standard result of comparison
	 */
	public int compareTo(RegisterSpec other) {
		if (this.reg < other.reg) {
			return -1;
		} else if (this.reg > other.reg) {
			return 1;
		}

		int compare = type.getType().compareTo(other.type.getType());

		if (compare != 0) {
			return compare;
		}

		if (this.local == null) {
			return (other.local == null) ? 0 : -1;
		} else if (other.local == null) {
			return 1;
		}

		return this.local.compareTo(other.local);
	}

	/** {@inheritDoc} */
	@Override
	public int hashCode() {
		return hashCodeOf(reg, type, local);
	}

	/**
	 * Helper for {@link #hashCode} and {@link #ForComparison.hashCode}, which actually
	 * does the calculation.
	 * 
	 * @param reg
	 *            value of the instance variable
	 * @param type
	 *            value of the instance variable
	 * @param local
	 *            value of the instance variable
	 * @return the hash code
	 */
	private static int hashCodeOf(int reg, TypeBearer type, LocalItem local) {
		int hash = (local != null) ? local.hashCode() : 0;

		hash = (hash * 31 + type.hashCode()) * 31 + reg;
		return hash;
	}

	/** {@inheritDoc} */
	@Override
	public String toString() {
		return toString0(false);
	}

	/** {@inheritDoc} */
	public String toHuman() {
		return toString0(true);
	}

	/** {@inheritDoc} */
	public Type getType() {
		return type.getType();
	}

	/** {@inheritDoc} */
	public TypeBearer getFrameType() {
		return type.getFrameType();
	}

	/** {@inheritDoc} */
	public final int getBasicType() {
		return type.getBasicType();
	}

	/** {@inheritDoc} */
	public final int getBasicFrameType() {
		return type.getBasicFrameType();
	}

	/** {@inheritDoc} */
	public final boolean isConstant() {
		return false;
	}

	/**
	 * Gets the register number.
	 * 
	 * @return {@code >= 0;} the register number
	 */
	public int getReg() {
		return reg;
	}

	/**
	 * Gets the type (or actual value) which is loaded from or stored to the
	 * register associated with this instance.
	 * 
	 * @return {@code non-null;} the type
	 */
	public TypeBearer getTypeBearer() {
		return type;
	}

	/**
	 * Gets the variable info associated with this instance, if any.
	 * 
	 * @return {@code null-ok;} the variable info, or {@code null} if this
	 *         instance has none
	 */
	public LocalItem getLocalItem() {
		return local;
	}

	/**
	 * Gets the next available register number after the one in this instance.
	 * This is equal to the register number plus the width (category) of the
	 * type used. Among other things, this may also be used to determine the
	 * minimum required register count implied by this instance.
	 * 
	 * @return {@code >= 0;} the required registers size
	 */
	public int getNextReg() {
		return reg + getCategory();
	}

	/**
	 * Gets the category of this instance's type. This is just a convenient
	 * shorthand for {@code getType().getCategory()}.
	 * 
	 * @see #isCategory1
	 * @see #isCategory2
	 * @return {@code 1..2;} the category of this instance's type
	 */
	public int getCategory() {
		return type.getType().getCategory();
	}

	/**
	 * Gets whether this instance's type is category 1. This is just a
	 * convenient shorthand for {@code getType().isCategory1()}.
	 * 
	 * @see #getCategory
	 * @see #isCategory2
	 * @return whether or not this instance's type is of category 1
	 */
	public boolean isCategory1() {
		return type.getType().isCategory1();
	}

	/**
	 * Gets whether this instance's type is category 2. This is just a
	 * convenient shorthand for {@code getType().isCategory2()}.
	 * 
	 * @see #getCategory
	 * @see #isCategory1
	 * @return whether or not this instance's type is of category 2
	 */
	public boolean isCategory2() {
		return type.getType().isCategory2();
	}

	/**
	 * Gets the string form for just the register number of this instance.
	 * 
	 * @return {@code non-null;} the register string form
	 */
	public String regString() {
		return regString(reg);
	}

	/**
	 * Returns an instance that is the intersection between this instance and
	 * the given one, if any. The intersection is defined as follows:
	 * <ul>
	 * <li>If {@code other} is {@code null}, then the result is {@code null}.
	 * <li>If the register numbers don't match, then the intersection is
	 * {@code null}. Otherwise, the register number of the intersection is the
	 * same as the one in the two instances.</li>
	 * <li>If the types returned by {@code getType()} are not {@code equals()},
	 * then the intersection is null.</li>
	 * <li>If the type bearers returned by {@code getTypeBearer()} are
	 * {@code equals()}, then the intersection's type bearer is the one from
	 * this instance. Otherwise, the intersection's type bearer is the
	 * {@code getType()} of this instance.</li>
	 * <li>If the locals are {@code equals()}, then the local info of the
	 * intersection is the local info of this instance. Otherwise, the local
	 * info of the intersection is {@code null}.</li>
	 * </ul>
	 * 
	 * @param other
	 *            {@code null-ok;} instance to intersect with (or {@code null} )
	 * @param localPrimary
	 *            whether local variables are primary to the intersection; if
	 *            {@code true}, then the only non-null results occur when
	 *            registers being intersected have equal local infos (or both
	 *            have {@code null} local infos)
	 * @return {@code null-ok;} the intersection
	 */
	public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) {
		if (this == other) {
			// Easy out.
			return this;
		}

		if ((other == null) || (reg != other.getReg())) {
			return null;
		}

		LocalItem resultLocal = ((local == null) || !local.equals(other
				.getLocalItem())) ? null : local;
		boolean sameName = (resultLocal == local);

		if (localPrimary && !sameName) {
			return null;
		}

		Type thisType = getType();
		Type otherType = other.getType();

		// Note: Types are always interned.
		if (thisType != otherType) {
			return null;
		}

		TypeBearer resultTypeBearer = type.equals(other.getTypeBearer()) ? type
				: thisType;

		if ((resultTypeBearer == type) && sameName) {
			// It turns out that the intersection is "this" after all.
			return this;
		}

		return (resultLocal == null) ? make(reg, resultTypeBearer) : make(reg,
				resultTypeBearer, resultLocal);
	}

	/**
	 * Returns an instance that is identical to this one, except that the
	 * register number is replaced by the given one.
	 * 
	 * @param newReg
	 *            {@code >= 0;} the new register number
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpec withReg(int newReg) {
		if (reg == newReg) {
			return this;
		}

		return makeLocalOptional(newReg, type, local);
	}

	/**
	 * Returns an instance that is identical to this one, except that the type
	 * is replaced by the given one.
	 * 
	 * @param newType
	 *            {@code non-null;} the new type
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpec withType(TypeBearer newType) {
		return makeLocalOptional(reg, newType, local);
	}

	/**
	 * Returns an instance that is identical to this one, except that the
	 * register number is offset by the given amount.
	 * 
	 * @param delta
	 *            the amount to offset the register number by
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpec withOffset(int delta) {
		if (delta == 0) {
			return this;
		}

		return withReg(reg + delta);
	}

	/**
	 * Returns an instance that is identical to this one, except that the type
	 * bearer is replaced by the actual underlying type (thereby stripping off
	 * non-type information) with any initialization information stripped away
	 * as well.
	 * 
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public RegisterSpec withSimpleType() {
		TypeBearer orig = type;
		Type newType;

		if (orig instanceof Type) {
			newType = (Type) orig;
		} else {
			newType = orig.getType();
		}

		if (newType.isUninitialized()) {
			newType = newType.getInitializedType();
		}

		if (newType == orig) {
			return this;
		}

		return makeLocalOptional(reg, newType, local);
	}

	/**
	 * Returns an instance that is identical to this one except that the local
	 * variable is as specified in the parameter.
	 * 
	 * @param local
	 *            {@code null-ok;} the local item or null for none
	 * @return an appropriate instance
	 */
	public RegisterSpec withLocalItem(LocalItem local) {
		if ((this.local == local)
				|| ((this.local != null) && this.local.equals(local))) {

			return this;
		}

		return makeLocalOptional(reg, type, local);
	}

	/**
	 * Helper for {@link #toString} and {@link #toHuman}.
	 * 
	 * @param human
	 *            whether to be human-oriented
	 * @return {@code non-null;} the string form
	 */
	private String toString0(boolean human) {
		StringBuffer sb = new StringBuffer(40);

		sb.append(regString());
		sb.append(":");

		if (local != null) {
			sb.append(local.toString());
		}

		Type justType = type.getType();
		sb.append(justType);

		if (justType != type) {
			sb.append("=");
			if (human && (type instanceof CstString)) {
				sb.append(((CstString) type).toQuoted());
			} else if (human && (type instanceof Constant)) {
				sb.append(type.toHuman());
			} else {
				sb.append(type);
			}
		}

		return sb.toString();
	}

	/**
	 * Holder of register spec data for the purposes of comparison (so that
	 * {@code RegisterSpec} itself can still keep {@code final} instance
	 * variables.
	 */
	private static class ForComparison {

		/** {@code >= 0;} register number */
		private int reg;

		/** {@code non-null;} type loaded or stored */
		private TypeBearer type;

		/**
		 * {@code null-ok;} local variable associated with this register, if any
		 */
		private LocalItem local;

		/**
		 * Set all the instance variables.
		 * 
		 * @param reg
		 *            {@code >= 0;} the register number
		 * @param type
		 *            {@code non-null;} the type (or possibly actual value)
		 *            which is loaded from or stored to the indicated register
		 * @param local
		 *            {@code null-ok;} the associated local variable, if any
		 * @return {@code non-null;} an appropriately-constructed instance
		 */
		public void set(int reg, TypeBearer type, LocalItem local) {
			this.reg = reg;
			this.type = type;
			this.local = local;
		}

		/**
		 * Construct a {@code RegisterSpec} of this instance's contents.
		 * 
		 * @return {@code non-null;} an appropriately-constructed instance
		 */
		public RegisterSpec toRegisterSpec() {
			return new RegisterSpec(reg, type, local);
		}

		/** {@inheritDoc} */
		@Override
		public boolean equals(Object other) {
			if (!(other instanceof RegisterSpec)) {
				return false;
			}

			RegisterSpec spec = (RegisterSpec) other;
			return spec.equals(reg, type, local);
		}

		/** {@inheritDoc} */
		@Override
		public int hashCode() {
			return hashCodeOf(reg, type, local);
		}
	}
}
